Skip to content

feat(land): the quest watcher bumps quest's pin in other repositories - #550

Merged
kixelated merged 16 commits into
mainfrom
quest/m0/pin-sync
Nov 8, 2026
Merged

kixelated merged 16 commits into
mainfrom
quest/m0/pin-sync

Conversation

@kixelated

@kixelated kixelated commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

Part of quest/m0/pin-sync.md: every push to quest's main now reaches each repository that pins quest without anyone touching it. The host half (the watcher's --bump flags and system.autoUpgrade) is a kixelated/nixos PR. This PR rewrites the quest to that remaining host work and the maintainer-only steps.

Builds on moq-dev/quest#540, now merged into main.

Decisions

  • Round-3 design checkpoint, how an ungated bump judges a run that a newer run cancelled (maintainer, 2026-11-08, through /quest-unblock, comment): ✅ continue, judging only each workflow's latest run per workflow and event, so a run cancelled because a newer run replaced it counts as replaced, not failed (rejected: stop merging bumps on ungated bases; rethink the targets, giving kixel.ai a required check and pushing nixos's bump straight to main; merge as is). As implemented, a cancelled run also yields to a newer run of its workflow from the other event, since the case that raised it is a cancel-in-progress group shared across push and pull_request, and the replaced run's check runs are dropped with it, so a later job it never started can't fail the bump.

What changed

  • quest land watch --bump <owner/repo> (repeatable, new src/cli/land-bump.ts). After its queue pass, each pass reads the watched base's commit. For each target that doesn't pin it yet, the watcher opens or updates that target's one quest-bump PR as the App:
    • It reads the target's flake.lock on its default branch and finds the one root input that locks the watched repository. A target already at the commit gets nothing, and an open bump PR there is closed with a comment.
    • In a fresh checkout that borrows its objects from a bare per-target cache with the base's whole history (so quest check --fix sees deleted quests, and later fetches are incremental), it unpins the URL in flake.nix (github:moq-dev/quest/<rev> becomes github:moq-dev/quest), so later bumps change only the lock. It then locks the input to the exact commit with nix flake lock --override-input.
    • It builds that commit's quest with nix build, then runs its quest init and quest check --fix in the checkout.
    • It commits as moq-bot and lease-pushes. When a bump PR is already open, the new commit is a merge of that PR's head and the base, with the fresh bump as its tree, so the branch never needs a force push. Auto-merge is disarmed before the push, the PR is re-read so one that closed meanwhile restarts the bump next pass, and a PR left on an old default branch is retargeted.
    • The body links the comparison and records the review as skipped (Skipped: pin bump, no repository code changed; CI's quest check is the gate.).
    • Findings --fix can't resolve leave the PR a draft with a ## Waiting item. Otherwise the PR lands through quest merge's own path (land()): GitHub's merge queue, the quest land gate or own queue, or auto-merge.
    • On a base that requires no checks, land() would merge immediately. There the bump first waits for every check on its head to pass (only each workflow's latest run per event counts, and a run a newer one cancelled counts as replaced), then merges directly if the repository has no auto-merge (kixelated/nixos, a private repository on GitHub Free). A failed check or a refused landing gets a comment and is left for a maintainer.
    • bump.json in the state directory records each step, so a restart repeats nothing, a failed landing never re-pushes, and a bump someone closed isn't reopened for the same commit. One target's failure doesn't hold up the others; the pass reports it and the daemon retries.
  • requirements() (used by quest merge): a private repository on GitHub Free answers GET /rules/branches with a 403 upgrade notice. That now reads as "no rules" instead of failing. On kixelated/nixos, quest merge now gives its usual "auto-merge disabled" refusal instead of an HTTP 403.
  • README: new "Pin bumps" section under the watcher.

Public API

  • New quest land watch --bump <owner/repo> option. The watcher refuses to start with it when nix isn't on PATH, or when it names the watched repository itself. Nothing else changes for existing invocations.
  • quest merge on a private repository on GitHub Free: refusal message instead of an HTTP 403 (above).
  • Each target's App token asks for contents: write, pull_requests: write, merge_queues: write, and read access to administration, checks, and statuses, plus workflows: write only for a push that carries a workflow change. All of these are within moq-bot's existing moq-dev installation. Nix reads GitHub with the watched repository's contents: read token.

Choices made without asking

  • One central pass in the quest repository's watcher, minting a token per target, rather than each repository's daemon bumping itself. The trigger is the watcher's own base, quest builds once per commit, and the pass covers targets with no land daemon (kixelated/nixos, studio, kixel.ai). The target list is just the --bump flags. The nixos module derives it from the enrolled land repositories, plus studio, kixel.ai, and nixos.
  • The bot unpins rev-pinned URLs on its first bump of each repository, following the quest's recommendation. moq, studio, kixel.ai, rind, rust2, and nixos pin a revision in flake.nix today.
  • The bump runs quest at the new commit, built by Nix. Using the watcher's own binary would write stubs from an older quest whenever the host lags behind quest's main. That code runs as the daemon user, which can read the App key. It's the same trust the daemon already places in reviewed main.
  • A newer bump replaces the PR's tree. A land fixer's commit on an older bump (such as moq.pro's instruction-budget fix in its manual bump) is dropped, and the target's fixer redoes it if the new bump needs it.
  • Ungated bases wait for every check, not just required ones. kixel.ai's only check is CodeRabbit (a neutral status), so its bumps merge once CodeRabbit reports.
  • Not added: compare-based ordering (the bot follows main even if a target pins a commit main doesn't contain), and a timeout on Nix commands (repository rule: no timeouts as workarounds).

Validation

  • just check and just test pass (1219 passed and 5 skipped, plus 562 passed), after merging origin/main.
  • Review fixes so far: the bump's quest runs from a directory of its own with --root (a target's bunfig.toml preload never loads); a checkout with a tracked symlink that leaves it or dangles is refused; an ungated bump waits for the head's workflow runs and for other apps' check suites; unpinning matches case-insensitively. The target token now also asks for actions: read.
  • test/land-bump.test.ts runs the pass against a fake GitHub and a local target repository with Nix and quest faked. It covers:
    • opening and queueing a bump, then a no-op second pass with no requests;
    • moving an open bump forward with a merge, after disarming auto-merge;
    • a findings draft;
    • closing a bump the base already pins;
    • an ungated base waiting for its checks before a direct merge;
    • a failed check left for a maintainer;
    • a run a newer run cancelled counting as replaced, and latestRuns's choice per workflow and event;
    • a symlink whose name holds a tab, full history for quest check --fix, a PR that closes mid-bump, and retargeting to a new default branch, and rebuilding a cached quest that Nix's garbage collection removed;
    • one failing target not holding up the others;
    • the GitHub Free 403 in requirements().
  • Live run of the real hostTools against a shallow clone of moq-dev/studio (nothing pushed): it found input quest at 1c3cf6a pinned in flake.nix, unpinned it, locked it to 6f20c25 (flake.lock 4 lines added and 5 removed), built quest, and ran quest init (nothing to change) and quest check --fix (35 documents ok).

Suggested follow-ups

  • None recommended beyond the rewritten quest's remaining host work.

Review

Rewritten by quest review after each round.

  • Reviewer: Codex, gpt-6.1-sol, xhigh effort
  • Session: 01a1b980-e3ee-7eb2-958a-fd82f75b9c42, round 4
  • Head: c73bca7
  • Base: origin/main at a1d71f7
  • Score: 10/10, The cache rebuild change introduces no findings; targeted read-only checks and diff checks passed, while full suites were not run in the read-only sandbox.
  • Findings: none

Models

  • Size: L
  • Implementer: Claude Code, claude-opus-5-5, high effort
  • Implementer: Claude Code, claude-opus-5-5, low effort (iterate, L)

🤖 Generated with Claude Code

(Written by Claude Opus 5.5)

@pullfrog

pullfrog Bot commented Nov 7, 2026

Copy link
Copy Markdown

Pullfrog  | Review this ➔

kixelated and others added 4 commits November 7, 2026 11:32
quest land watch --bump <owner/repo> keeps each target's flake input on the
watched repository at the watched base: it opens or updates one quest-bump PR
per target as the App, with the input unpinned from flake.nix, locked by Nix,
and quest init and quest check --fix run with the new quest, then lands it
through quest merge's path. requirements() reads no rules from a private
repository on GitHub Free instead of failing on its 403.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	quest/m0/queue-watcher/own-queue.md
… finishes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title Repositories and hosts follow quest's main feat(land): the quest watcher bumps quest's pin in other repositories Nov 7, 2026
kixelated and others added 2 commits November 7, 2026 12:14
… ungated bases

Run the bump's quest from a directory of its own with --root, refuse a checkout
with a symlink that leaves it or dangles, require every check suite that ran a
check to complete before merging an ungated bump, and unpin a URL in any case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Design change: Continue. Judge only the latest workflow run per workflow and event, so a run cancelled because a newer run replaced it counts as replaced, not failed. The maintainer picked this at the round-3 design checkpoint through /quest-unblock on 2026-11-08 (rejected: stop merging bumps on ungated bases; rethink the targets; merge as is).

(Written by Claude Opus 5.5)

…s replaced

Resolves conflicts with the per-base watcher and log pruning. settle now judges
only each workflow's latest run per event, and a cancelled run yields to a newer
run of its workflow from another event (a shared cancel-in-progress group across
push and pull_request), along with that run's check runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated and others added 2 commits November 7, 2026 21:13
…llows its PR

- contained() splits each ls-files entry at its first tab, so a symlink
  whose name holds a tab is checked under its own path.
- A bare cache per target holds the base's whole history, which quest
  check --fix reads for deleted quests, and makes later fetches incremental.
- The open bump PR is re-read before the push, and a PR that closed during
  the Nix work fails the pass, which starts over next time.
- An open bump PR is retargeted when the target's default branch moved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review November 8, 2026 05:28
@coderabbitai

coderabbitai Bot commented Nov 8, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 10 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 9ae176ec-45c5-4afa-9be2-f7554ae95ef0
Commits

Reviewing files that changed from the base of the PR and between 738bbde and c73bca7.

Files selected for processing: 8

  • README.md
  • quest/m0/pin-sync.md
  • src/cli/github.ts
  • src/cli/land-bump.ts
  • src/cli/land-watch.ts
  • src/cli/land.ts
  • src/cli/main.ts
  • test/land-bump.test.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Head: 7ca104c
Base: 738bbde

Found one P2 issue in the lifetime of the cached Nix-built executable. Please keep the cached output rooted while it is in use, or rebuild when the cached executable disappears, with a regression test for retrying the same source commit after the cached output is removed.

Reviewed the pin-bump implementation and tests, watcher/CLI integration, branch-rules fallback, and the existing landing/check helpers. This was a GitHub-only static review: no checkout, shell commands, builds, or tests were run. The supplied tests fake Nix and quest, so they do not exercise hostTools' output lifetime.

(Written by review (OpenAI))

Comment thread src/cli/land-bump.ts Resolved
nix build --no-link roots nothing, so a retry for the same source commit
could find the cached executable gone and fail until the watcher restarts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Head: c73bca7
Base: 738bbde

Follow-up to #550 (review). The base is unchanged; the only intervening commit changes hostTools' cache check and adds its regression test.

The earlier P2 is addressed: hostTools.quest now rebuilds when the cached executable is absent, so a later call for the same source SHA can recover after garbage collection. The new test exercises the real hostTools with a fake nix on PATH, verifies reuse while the output exists, removes the output, and verifies recreation with exactly two builds. No new actionable findings in this follow-up; no further change requested for the reported cache issue.

GitHub-only static review of the incremental diff and surrounding hostTools/environment code. No checkout, shell commands, builds, or tests were run; the test was inspected, not executed, and actual Nix garbage collection was not exercised.

(Written by review (OpenAI))

@kixelated
kixelated added this pull request to the merge queue Nov 8, 2026
Merged via the queue into main with commit 19c6602 Nov 8, 2026
17 checks passed
@kixelated
kixelated deleted the quest/m0/pin-sync branch November 8, 2026 06:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant